/*
 * Copyright 2007 Rob Nielsen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.proxymatic.apps.interfacer;

import com.googlecode.proxymatic.core.AbstractProxyApplication;
import com.googlecode.proxymatic.core.HandlerConfiguration;
import com.googlecode.proxymatic.core.handlers.method.MatchingInterfaceMethodHandler;
import com.googlecode.proxymatic.core.handlers.method.SameInterfaceMethodHandler;
import com.googlecode.proxymatic.core.util.ReflectUtil;

public class ProxyDynamicInterfacer extends AbstractProxyApplication implements DynamicInterfacer {

    protected void setUpHandlerConfiguration(HandlerConfiguration handlerConfiguration) {
        handlerConfiguration.addHandler(new SameInterfaceMethodHandler());
        handlerConfiguration.addHandler(new MatchingInterfaceMethodHandler());
    }

    protected String getProxyType() {
        return "Dynamic Interfacer";
    }

    /**
     * Casts an object as a given interface.  The object must implement the methods in the interface but does not have
     * to implement the interface directly.  This allows you to retrofit interfaces on existing classes which do not
     * implement them.  The returned object will only implement this interface.
     *
     * @param targetInterface the desired interface
     * @param objectToCast    the object to be cast as targetInterface
     * @return an instance of targetInterface which calls through to to the given object when it's methods are called.
     */
    public <T> T cast(Class<T> targetInterface, Object objectToCast) {
        return aggregate(targetInterface, new Object[]{objectToCast});
    }

    /**
     * Adds an interface to a given object.  The object must implement all the methods in the interface but does not
     * have to implement the interface directly.  This allows you to retrofit interfaces on existing classes which do
     * not implement them.  The returned object will also implement any interfaces implemented by the object.
     *
     * @param targetInterface the desired interface
     * @param object          the object to add targetInterface to.
     * @return an instance of targetInterface which calls through to to the given object when it's methods are called.
     */
    public <T> T add(Class<T> targetInterface, Object object) {
        Object[] objectsToAggregate = {object};
        Class[] interfaces = ReflectUtil.createInterfaceArray(objectsToAggregate, new Class[]{targetInterface});
        return targetInterface.cast(create(proxy().implementingInterfaces(interfaces).withImplementationObjects(objectsToAggregate)));
    }

    /**
     * Aggregates a number of objects under a single interface.  The objects need not actually implement the methods
     * directly, just have a matching signature.  If any of the methods in the interface are not found in one of the
     * objectsToAggregate, an IllegalArgumentException will be thrown.
     *
     * @param targetInterface    the desired interface
     * @param objectsToAggregate a collection of objects
     * @return an instance of targetInterface backed by the collection of objectsToAggregate
     */
    public <T> T aggregate(Class<T> targetInterface, Object[] objectsToAggregate) {
        return targetInterface.cast(create(proxy().implementingInterface(targetInterface)
                .withImplementationObjects(objectsToAggregate)));
    }
}
